--- title: "03-Spring AOP(面向切面编程)" created: 2025-11-26 tags: - Java --- # Spring AOP(面向切面编程) > [!note] 定位说明 > Spring 系列第 3 篇。核心回答:**日志/事务这类横切逻辑怎么不侵入业务**——九部分从问题提出到核心概念/术语、实现原理(JDK 动态代理 vs CGLIB)、五种通知类型、切入点表达式、实际应用、执行顺序与最佳实践。前置:[[05-动态代理|动态代理]]。 ## 第一部分:问题的提出 ### 一、传统方式的困境 想象一个场景: ```java // 传统方式:业务代码中混入大量横切关注点 @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping public void createUser(User user) { // 日志:记录请求 System.out.println("创建用户请求:" + user); // 权限检查 if (!hasPermission()) { throw new SecurityException("无权限"); } // 性能监控:记录开始时间 long startTime = System.currentTimeMillis(); try { // 业务逻辑 userService.createUser(user); // 日志:记录成功 System.out.println("用户创建成功"); } catch (Exception e) { // 异常处理 System.out.println("用户创建失败:" + e.getMessage()); throw e; } finally { // 性能监控:记录耗时 long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); } } @GetMapping("/{id}") public User getUser(@PathVariable int id) { // 又要重复写一遍日志、权限、性能监控的代码... System.out.println("查询用户请求:" + id); if (!hasPermission()) { throw new SecurityException("无权限"); } long startTime = System.currentTimeMillis(); try { User user = userService.getUser(id); System.out.println("用户查询成功"); return user; } catch (Exception e) { System.out.println("用户查询失败:" + e.getMessage()); throw e; } finally { long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); } } @DeleteMapping("/{id}") public void deleteUser(@PathVariable int id) { // 又要重复写一遍... } } ``` 这种方式有什么问题? 1. **代码重复** - 日志、权限、性能监控代码在每个方法中重复 2. **职责混乱** - 业务逻辑和增强逻辑混在一起 3. **难以维护** - 修改增强逻辑需要改动所有方法 4. **难以测试** - 无法单独测试业务逻辑 5. **耦合度高** - 业务代码依赖于增强逻辑 ### 二、理想的解决方案 我们希望能这样写: ```java // 理想方式:业务代码只关注业务逻辑 @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping public void createUser(User user) { // 只写业务逻辑,增强逻辑由AOP自动处理 userService.createUser(user); } @GetMapping("/{id}") public User getUser(@PathVariable int id) { // 只写业务逻辑 return userService.getUser(id); } @DeleteMapping("/{id}") public void deleteUser(@PathVariable int id) { // 只写业务逻辑 userService.deleteUser(id); } } // 通过AOP自动处理日志、权限、性能监控等 ``` **这就是AOP要解决的问题:在不改变原始代码的前提下,为方法自动织入增强逻辑。** --- ## 第二部分:AOP的核心概念 ### 三、什么是AOP? #### 3.1 AOP的定义 **AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,通过在程序运行时动态地将代码切入到指定的位置,实现对横切关注点的统一处理。** ``` ┌──────────────────────────────────────────┐ │ AOP的本质 │ ├──────────────────────────────────────────┤ │ │ │ 问题: │ │ ├─ 日志、权限、事务等逻辑 │ │ ├─ 与具体业务无关 │ │ ├─ 但所有方法都需要 │ │ └─ 导致代码重复、耦合高 │ │ │ │ 解决方案: │ │ ├─ 将这些逻辑抽离出来 │ │ ├─ 统一成"切面" │ │ ├─ 通过代理模式织入 │ │ └─ 实现"非侵入式编程" │ │ │ │ 核心思想: │ │ └─ "在什么时候,在什么位置, │ │ 做什么事情" │ │ │ └──────────────────────────────────────────┘ ``` #### 3.2 AOP与[[06-面向对象编程(OOP)|面向对象编程(OOP)]]的关系 ``` ┌──────────────────────────────────────────┐ │ AOP与OOP的关系 │ ├──────────────────────────────────────────┤ │ │ │ OOP(面向对象编程) │ │ ├─ 纵向:类的继承、多态 │ │ ├─ 解决:代码复用、结构清晰 │ │ └─ 特点:纵向扩展 │ │ │ │ AOP(面向切面编程) │ │ ├─ 横向:切面的织入 │ │ ├─ 解决:横切关注点 │ │ └─ 特点:横向扩展 │ │ │ │ 关系: │ │ └─ AOP是OOP的补充和扩展 │ │ OOP处理纵向,AOP处理横向 │ │ │ └──────────────────────────────────────────┘ ``` #### 3.3 AOP的核心优势 ``` ┌──────────────────────────────────────────┐ │ AOP的核心优势 │ ├──────────────────────────────────────────┤ │ │ │ 1. 解耦 │ │ ├─ 业务逻辑和增强逻辑分离 │ │ ├─ 易于维护和扩展 │ │ └─ 符合单一职责原则 │ │ │ │ 2. 非侵入式编程 │ │ ├─ 不改动原始代码 │ │ ├─ 通过配置织入增强逻辑 │ │ └─ 想加就加,想撕就撕 │ │ │ │ 3. 代码复用 │ │ ├─ 一个切面适用多个方法 │ │ ├─ 减少代码重复 │ │ └─ 易于维护 │ │ │ │ 4. 灵活性 │ │ ├─ 支持多个切面组合 │ │ ├─ 支持切面顺序控制 │ │ └─ 支持条件织入 │ │ │ │ 5. 可维护性 │ │ ├─ 增强逻辑集中管理 │ │ ├─ 易于修改和扩展 │ │ └─ 代码更清晰 │ │ │ └──────────────────────────────────────────┘ ``` --- ## 第三部分:AOP的核心术语 ### 四、AOP的专业术语 #### 4.1 术语解释 ``` ┌──────────────────────────────────────────┐ │ AOP的核心术语 │ ├──────────────────────────────────────────┤ │ │ │ 连接点(JoinPoint) │ │ ├─ 定义:所有能被增强的位置 │ │ ├─ 包括:方法、构造器、字段 │ │ └─ 例子:UserService的所有方法 │ │ │ │ 切入点(Pointcut) │ │ ├─ 定义:我们实际选中的位置 │ │ ├─ 包括:特定的方法、包下的所有方法 │ │ └─ 例子:UserService.createUser() │ │ │ │ 通知(Advice) │ │ ├─ 定义:真正的增强逻辑 │ │ ├─ 包括:日志、权限、事务等 │ │ └─ 例子:打印日志的代码 │ │ │ │ 切面(Aspect) │ │ ├─ 定义:通知+切入点的组合 │ │ ├─ 包括:在哪里、什么时候、做什么 │ │ └─ 例子:日志切面 │ │ │ │ 目标对象(Target) │ │ ├─ 定义:被增强的原始对象 │ │ ├─ 包括:UserService实例 │ │ └─ 特点:不知道自己被增强了 │ │ │ │ 代理对象(Proxy) │ │ ├─ 定义:加完增强的对象 │ │ ├─ 包括:由Spring创建的代理类实例 │ │ └─ 特点:对外使用的是这个对象 │ │ │ │ 织入(Weaving) │ │ ├─ 定义:将增强逻辑应用到目标的过程 │ │ ├─ 包括:编译期、类加载期、运行期 │ │ └─ Spring:运行期织入 │ │ │ └──────────────────────────────────────────┘ ``` #### 4.2 术语类比 ``` ┌──────────────────────────────────────────┐ │ AOP术语与"加特效"的类比 │ ├──────────────────────────────────────────┤ │ │ │ 术语 现实类比 │ │ ───────────────────────────────────── │ │ 连接点 能加特效的动作 │ │ (跳跃、攻击) │ │ │ │ 切入点 选中"攻击"动作 │ │ 加特效 │ │ │ │ 通知 加特效本身 │ │ (发光、爆炸) │ │ │ │ 切面 发光特效应用到 │ │ 攻击动作上 │ │ │ │ 目标对象 没有特效的 │ │ 普通角色 │ │ │ │ 代理对象 看上去很牛的 │ │ 角色 │ │ │ │ 织入 给角色加特效 │ │ 的过程 │ │ │ └──────────────────────────────────────────┘ ``` --- ## 第四部分:AOP的实现原理 ### 五、Spring AOP的实现方式 #### 5.1 两种代理方式 ``` ┌──────────────────────────────────────────┐ │ Spring AOP的两种代理方式 │ ├──────────────────────────────────────────┤ │ │ │ JDK动态代理 │ │ ├─ 原理:基于接口实现 │ │ ├─ 前提:目标类必须实现接口 │ │ ├─ 优点:原生支持,无需依赖 │ │ ├─ 缺点:只能代理接口方法 │ │ └─ 性能:反射调用,性能一般 │ │ │ │ CGLIB动态代理 │ │ ├─ 原理:通过继承+字节码增强 │ │ ├─ 前提:目标类可以没有接口 │ │ ├─ 优点:支持代理类,灵活性强 │ │ ├─ 缺点:需要CGLIB依赖 │ │ └─ 性能:字节码调用,性能较好 │ │ │ │ Spring的选择策略: │ │ ├─ Spring Framework:优先JDK │ │ └─ Spring Boot 2.x+:优先CGLIB │ │ │ └──────────────────────────────────────────┘ ``` #### 5.2 JDK动态代理实现AOP ```java // JDK动态代理实现AOP // 1. 定义业务接口 public interface UserService { void createUser(User user); User getUser(int id); } // 2. 实现业务接口 @Service public class UserServiceImpl implements UserService { @Override public void createUser(User user) { System.out.println("创建用户:" + user.getName()); } @Override public User getUser(int id) { System.out.println("查询用户:" + id); return new User(id, "Tom"); } } // 3. 创建代理对象 public class JDKProxyAOPDemo { public static void main(String[] args) { // 创建目标对象 UserService target = new UserServiceImpl(); // 创建代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, (proxyObj, method, args) -> { // 前置增强 System.out.println("=== 方法调用前 ==="); System.out.println("方法名:" + method.getName()); System.out.println("参数:" + Arrays.toString(args)); // 调用原方法 long startTime = System.currentTimeMillis(); Object result = method.invoke(target, args); long endTime = System.currentTimeMillis(); // 后置增强 System.out.println("=== 方法调用后 ==="); System.out.println("耗时:" + (endTime - startTime) + "ms"); System.out.println("返回值:" + result); return result; } ); // 调用代理对象的方法 proxy.createUser(new User(1, "Tom")); User user = proxy.getUser(1); } } ``` #### 5.3 CGLIB动态代理实现AOP ```java // CGLIB动态代理实现AOP // 1. 定义业务类(无需接口) @Service public class UserService { public void createUser(User user) { System.out.println("创建用户:" + user.getName()); } public User getUser(int id) { System.out.println("查询用户:" + id); return new User(id, "Tom"); } } // 2. 创建代理对象 public class CGLIBProxyAOPDemo { public static void main(String[] args) { // 创建目标对象 UserService target = new UserService(); // 创建Enhancer Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { // 前置增强 System.out.println("=== 方法调用前 ==="); System.out.println("方法名:" + method.getName()); System.out.println("参数:" + Arrays.toString(args)); // 调用原方法 long startTime = System.currentTimeMillis(); Object result = proxy.invokeSuper(obj, args); long endTime = System.currentTimeMillis(); // 后置增强 System.out.println("=== 方法调用后 ==="); System.out.println("耗时:" + (endTime - startTime) + "ms"); System.out.println("返回值:" + result); return result; }); // 创建代理对象 UserService proxy = (UserService) enhancer.create(); // 调用代理对象的方法 proxy.createUser(new User(1, "Tom")); User user = proxy.getUser(1); } } ``` --- ## 第五部分:AOP的通知类型 ### 六、五种通知类型 #### 6.1 通知类型概览 ``` ┌──────────────────────────────────────────┐ │ AOP的五种通知类型 │ ├──────────────────────────────────────────┤ │ │ │ @Before(前置通知) │ │ ├─ 执行时机:方法执行前 │ │ ├─ 用途:权限检查、参数验证 │ │ └─ 特点:无法获取返回值 │ │ │ │ @After(后置通知) │ │ ├─ 执行时机:方法执行后(无论异常) │ │ ├─ 用途:资源释放、日志记录 │ │ └─ 特点:总是执行 │ │ │ │ @AfterReturning(返回通知) │ │ ├─ 执行时机:方法成功返回后 │ │ ├─ 用途:处理返回值、缓存 │ │ └─ 特点:可以获取返回值 │ │ │ │ @AfterThrowing(异常通知) │ │ ├─ 执行时机:方法抛异常时 │ │ ├─ 用途:异常处理、告警 │ │ └─ 特点:可以获取异常信息 │ │ │ │ @Around(环绕通知) │ │ ├─ 执行时机:包裹整个方法执行 │ │ ├─ 用途:性能监控、事务管理 │ │ └─ 特点:最灵活,能实现所有通知效果 │ │ │ └──────────────────────────────────────────┘ ``` #### 6.2 通知执行顺序 ``` ┌──────────────────────────────────────────┐ │ 通知的执行顺序 │ ├──────────────────────────────────────────┤ │ │ │ 正常执行流程: │ │ ├─ @Before(前置) │ │ ├─ 执行原方法 │ │ ├─ @AfterReturning(返回) │ │ ├─ @After(后置) │ │ └─ 返回结果 │ │ │ │ 异常执行流程: │ │ ├─ @Before(前置) │ │ ├─ 执行原方法(抛异常) │ │ ├─ @AfterThrowing(异常) │ │ ├─ @After(后置) │ │ └─ 抛出异常 │ │ │ │ @Around的位置: │ │ └─ 包裹整个流程,最外层 │ │ │ └──────────────────────────────────────────┘ ``` #### 6.3 各通知类型的实现 ```java // 各通知类型的实现 @Aspect @Component public class UserServiceAspect { // 1. 前置通知:方法执行前 @Before("execution(* com.example.service.UserService.*(..))") public void beforeMethod(JoinPoint joinPoint) { System.out.println("=== 前置通知 ==="); System.out.println("方法名:" + joinPoint.getSignature().getName()); System.out.println("参数:" + Arrays.toString(joinPoint.getArgs())); } // 2. 后置通知:方法执行后(无论异常) @After("execution(* com.example.service.UserService.*(..))") public void afterMethod(JoinPoint joinPoint) { System.out.println("=== 后置通知 ==="); System.out.println("方法执行完成"); } // 3. 返回通知:方法成功返回后 @AfterReturning( pointcut = "execution(* com.example.service.UserService.*(..))", returning = "result" ) public void afterReturningMethod(JoinPoint joinPoint, Object result) { System.out.println("=== 返回通知 ==="); System.out.println("返回值:" + result); } // 4. 异常通知:方法抛异常时 @AfterThrowing( pointcut = "execution(* com.example.service.UserService.*(..))", throwing = "exception" ) public void afterThrowingMethod(JoinPoint joinPoint, Exception exception) { System.out.println("=== 异常通知 ==="); System.out.println("异常类型:" + exception.getClass().getName()); System.out.println("异常信息:" + exception.getMessage()); } // 5. 环绕通知:包裹整个方法执行 @Around("execution(* com.example.service.UserService.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("=== 环绕通知 - 前置 ==="); long startTime = System.currentTimeMillis(); try { // 执行原方法 Object result = joinPoint.proceed(); System.out.println("=== 环绕通知 - 返回 ==="); System.out.println("返回值:" + result); return result; } catch (Exception e) { System.out.println("=== 环绕通知 - 异常 ==="); System.out.println("异常:" + e.getMessage()); throw e; } finally { long endTime = System.currentTimeMillis(); System.out.println("=== 环绕通知 - 后置 ==="); System.out.println("耗时:" + (endTime - startTime) + "ms"); } } } ``` #### 6.4 推荐使用@Around ```java // 推荐使用@Around,因为它最灵活 @Aspect @Component public class BestPracticeAspect { // ✅ 推荐:使用@Around实现所有通知的效果 @Around("execution(* com.example.service.*.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { // 前置通知 System.out.println("前置:方法执行前"); long startTime = System.currentTimeMillis(); Object result = null; Exception exception = null; try { // 执行原方法 result = joinPoint.proceed(); // 返回通知 System.out.println("返回:" + result); return result; } catch (Exception e) { // 异常通知 exception = e; System.out.println("异常:" + e.getMessage()); throw e; } finally { // 后置通知 long endTime = System.currentTimeMillis(); System.out.println("后置:耗时" + (endTime - startTime) + "ms"); } } } ``` --- ## 第六部分:切入点表达式 ### 七、切入点表达式详解 #### 7.1 切入点表达式语法 ```java // 切入点表达式语法 // 基本格式:execution(修饰符 返回类型 包名.类名.方法名(参数)) // 1. 匹配所有方法 @Pointcut("execution(* *(..))") public void allMethods() {} // 2. 匹配特定包下的所有方法 @Pointcut("execution(* com.example.service.*.*(..))") public void servicePackage() {} // 3. 匹配特定类的所有方法 @Pointcut("execution(* com.example.service.UserService.*(..))") public void userServiceClass() {} // 4. 匹配特定方法 @Pointcut("execution(* com.example.service.UserService.createUser(..))") public void createUserMethod() {} // 5. 匹配特定参数的方法 @Pointcut("execution(* com.example.service.UserService.createUser(com.example.User))") public void createUserWithUserParam() {} // 6. 匹配任意参数的方法 @Pointcut("execution(* com.example.service.UserService.*(..))") public void anyParams() {} // 7. 匹配特定返回类型的方法 @Pointcut("execution(com.example.User com.example.service.UserService.getUser(..))") public void returnUserType() {} // 8. 匹配void返回的方法 @Pointcut("execution(void com.example.service.UserService.*(..))") public void voidReturn() {} // 9. 匹配特定注解的方法 @Pointcut("@annotation(com.example.annotation.RequirePermission)") public void annotatedMethod() {} // 10. 匹配特定注解的类 @Pointcut("@within(com.example.annotation.Service)") public void annotatedClass() {} // 11. 组合切入点 @Pointcut("execution(* com.example.service.*.*(..)) && @annotation(com.example.annotation.Log)") public void serviceAndAnnotated() {} ``` #### 7.2 常用切入点表达式 ```java // 常用的切入点表达式 @Aspect @Component public class CommonPointcutExpressions { // 1. 匹配service包下所有类的所有方法 @Pointcut("execution(* com.example.service..*.*(..))") public void serviceLayer() {} // 2. 匹配controller包下所有类的所有方法 @Pointcut("execution(* com.example.controller..*.*(..))") public void controllerLayer() {} // 3. 匹配所有@Service注解的类 @Pointcut("@within(org.springframework.stereotype.Service)") public void serviceAnnotation() {} // 4. 匹配所有@RequestMapping注解的方法 @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public void requestMappingAnnotation() {} // 5. 匹配所有public方法 @Pointcut("execution(public * com.example..*.*(..))") public void publicMethods() {} // 6. 匹配所有protected方法 @Pointcut("execution(protected * com.example..*.*(..))") public void protectedMethods() {} // 7. 匹配所有返回User对象的方法 @Pointcut("execution(com.example.User com.example..*.*(..))") public void returnUser() {} // 8. 匹配所有参数为User的方法 @Pointcut("execution(* com.example..*(com.example.User))") public void paramUser() {} // 9. 匹配所有参数为User和String的方法 @Pointcut("execution(* com.example..*(com.example.User, String))") public void paramUserAndString() {} // 10. 匹配所有参数个数为2的方法 @Pointcut("execution(* com.example..*(*, *))") public void twoParams() {} } ``` --- ## 第七部分:AOP的实际应用 ### 八、日志切面 ```java // 日志切面实现 @Aspect @Component public class LoggingAspect { private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); @Around("execution(* com.example.service..*.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法信息 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String className = joinPoint.getTarget().getClass().getSimpleName(); String methodName = signature.getName(); Object[] args = joinPoint.getArgs(); // 记录方法调用前 logger.info("=== 方法调用开始 ==="); logger.info("类名:{}", className); logger.info("方法名:{}", methodName); logger.info("参数:{}", Arrays.toString(args)); long startTime = System.currentTimeMillis(); try { // 执行原方法 Object result = joinPoint.proceed(); // 记录返回值 logger.info("返回值:{}", result); return result; } catch (Exception e) { // 记录异常 logger.error("方法执行异常:{}", e.getMessage(), e); throw e; } finally { // 记录耗时 long endTime = System.currentTimeMillis(); logger.info("耗时:{}ms", (endTime - startTime)); logger.info("=== 方法调用结束 ==="); } } } ``` ### 九、性能监控切面 ```java // 性能监控切面实现 @Aspect @Component public class PerformanceMonitoringAspect { private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringAspect.class); // 定义切入点 @Pointcut("execution(* com.example.service..*.*(..))") public void serviceLayer() {} @Around("serviceLayer()") public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); long startTime = System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long endTime = System.currentTimeMillis(); long duration = endTime - startTime; // 记录耗时 logger.info("方法:{},耗时:{}ms", methodName, duration); // 如果超过阈值,发送告警 if (duration > 1000) { logger.warn("方法{}执行过慢,耗时{}ms", methodName, duration); // 发送告警通知 sendAlert(methodName, duration); } } } private void sendAlert(String methodName, long duration) { // 发送告警逻辑 System.out.println("发送告警:方法" + methodName + "执行过慢"); } } ``` ### 十、权限检查切面 ```java // 权限检查切面实现 // 1. 定义权限注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); } // 2. 定义权限检查切面 @Aspect @Component public class PermissionCheckAspect { @Autowired private UserService userService; @Before("@annotation(requirePermission)") public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) { // 获取当前用户 User currentUser = getCurrentUser(); if (currentUser == null) { throw new SecurityException("用户未登录"); } // 检查权限 String requiredPermission = requirePermission.value(); if (!userService.hasPermission(currentUser.getId(), requiredPermission)) { throw new SecurityException("用户无权限:" + requiredPermission); } } private User getCurrentUser() { // 从SecurityContext获取当前用户 return null; // 实现细节 } } // 3. 使用权限注解 @Service public class UserService { @RequirePermission("DELETE_USER") public void deleteUser(int id) { System.out.println("删除用户:" + id); } @RequirePermission("UPDATE_USER") public void updateUser(User user) { System.out.println("更新用户:" + user.getName()); } } ``` ### 十一、事务管理切面 ```java // 事务管理切面实现 @Aspect @Component public class TransactionAspect { @Autowired private TransactionManager transactionManager; // 匹配所有@Transactional注解的方法 @Around("@annotation(org.springframework.transaction.annotation.Transactional)") public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable { // 开启事务 TransactionStatus status = transactionManager.getTransaction( new DefaultTransactionDefinition() ); try { // 执行业务逻辑 Object result = joinPoint.proceed(); // 提交事务 transactionManager.commit(status); return result; } catch (Exception e) { // 回滚事务 transactionManager.rollback(status); throw e; } } } // 使用@Transactional注解 @Service public class OrderService { @Transactional public void createOrder(Order order) { // 业务逻辑 // 如果发生异常,事务自动回滚 } } ``` ### 十二、缓存切面 ```java // 缓存切面实现 // 1. 定义缓存注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Cacheable { String value(); long timeout() default 3600; // 秒 } // 2. 定义缓存切面 @Aspect @Component public class CacheAspect { private final Map cache = new ConcurrentHashMap<>(); @Around("@annotation(cacheable)") public Object cacheMethod(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable { // 生成缓存key String cacheKey = generateCacheKey(joinPoint, cacheable); // 检查缓存 CacheEntry entry = cache.get(cacheKey); if (entry != null && !entry.isExpired()) { System.out.println("从缓存返回:" + cacheKey); return entry.getValue(); } // 执行原方法 Object result = joinPoint.proceed(); // 存入缓存 cache.put(cacheKey, new CacheEntry(result, cacheable.timeout())); return result; } private String generateCacheKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); return cacheable.value() + ":" + methodName + ":" + args; } static class CacheEntry { private final Object value; private final long expireTime; public CacheEntry(Object value, long timeout) { this.value = value; this.expireTime = System.currentTimeMillis() + timeout * 1000; } public boolean isExpired() { return System.currentTimeMillis() > expireTime; } public Object getValue() { return value; } } } // 3. 使用缓存注解 @Service public class UserService { @Cacheable("user") public User getUserById(int id) { System.out.println("从数据库查询用户:" + id); return new User(id, "Tom"); } } ``` --- ## 第八部分:切面的执行顺序 ### 十三、多切面的执行顺序 #### 13.1 使用@Order控制顺序 ```java // 多切面的执行顺序控制 // 1. 权限检查切面(最先执行) @Aspect @Component @Order(1) public class PermissionAspect { @Before("execution(* com.example.service.*.*(..))") public void checkPermission(JoinPoint joinPoint) { System.out.println("1. 权限检查"); } } // 2. 日志切面 @Aspect @Component @Order(2) public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logging(JoinPoint joinPoint) { System.out.println("2. 记录日志"); } } // 3. 性能监控切面(最后执行) @Aspect @Component @Order(3) public class PerformanceAspect { @Before("execution(* com.example.service.*.*(..))") public void monitoring(JoinPoint joinPoint) { System.out.println("3. 性能监控"); } } // 执行顺序: // 前置通知:1 → 2 → 3 // 执行原方法 // 后置通知:3 → 2 → 1 ``` #### 13.2 切面执行顺序的详细流程 ``` ┌──────────────────────────────────────────┐ │ 多切面的执行顺序 │ ├──────────────────────────────────────────┤ │ │ │ @Order(1) - PermissionAspect │ │ ├─ @Before:权限检查 │ │ │ │ │ @Order(2) - LoggingAspect │ │ ├─ @Before:记录日志 │ │ │ │ │ @Order(3) - PerformanceAspect │ │ ├─ @Before:性能监控 │ │ │ │ │ ├─ 执行原方法 │ │ │ │ │ @Order(3) - PerformanceAspect │ │ ├─ @After:性能监控后置 │ │ │ │ │ @Order(2) - LoggingAspect │ │ ├─ @After:日志后置 │ │ │ │ │ @Order(1) - PermissionAspect │ │ ├─ @After:权限检查后置 │ │ │ └──────────────────────────────────────────┘ ``` --- ## 第九部分:AOP的最佳实践 ### 十四、推荐做法 #### 14.1 优先使用 `@Around`,而不是同时写一堆通知 > 能用一个 `@Around` 表达的,就不要再拆成 `@Before` / `@AfterReturning` / `@AfterThrowing` / `@After` 四连。 ```java @Aspect @Component public class GoodAspect { // ✅ 推荐:使用 @Around 实现所有通知的效果 @Around("execution(* com.example.service.*.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("前置逻辑"); try { Object result = joinPoint.proceed(); // 执行业务方法 System.out.println("返回逻辑"); return result; } catch (Exception e) { System.out.println("异常逻辑"); throw e; } finally { System.out.println("后置逻辑"); } } } ``` - 实际上 `@Around` 可以覆盖所有类型通知; - 逻辑集中在一个地方,方便维护、调试; - 避免多个通知顺序、重复执行的问题。 --- #### 14.2 使用 `@Pointcut` 提取公共切入点 > 不要到处复制粘贴 `execution(...)`,统一抽成 `@Pointcut`,维护成本低很多。 ```java @Aspect @Component public class BestPracticeAspect { // Service 层切点 @Pointcut("execution(* com.example.service..*.*(..))") public void serviceLayer() {} // Controller 层切点 @Pointcut("execution(* com.example.controller..*.*(..))") public void controllerLayer() {} // 组合切点:Controller + Service @Pointcut("serviceLayer() || controllerLayer()") public void allLayers() {} @Around("serviceLayer()") public Object monitorService(ProceedingJoinPoint joinPoint) throws Throwable { // 可以加耗时统计、统一异常转换等 return joinPoint.proceed(); } @Around("controllerLayer()") public Object monitorController(ProceedingJoinPoint joinPoint) throws Throwable { // 可以加统一日志、请求参数记录等 return joinPoint.proceed(); } } ``` **关键点:** - 把「横切范围」抽象成名字(`serviceLayer`、`controllerLayer`),比一串 `execution(...)` 可读多了; - 支持组合切点:`&&`、`||`、`!`; - 后续包结构、类名改变,只用改一个地方的表达式。 --- #### 14.3 使用**自定义注解**定义“语义切点” > 比起按包名/类名切,更推荐按“业务语义”切,比如:`@OpLog`、`@Audit`、`@RateLimit`、`@DistributedLock` 等。 #### 1)定义自定义注解 ```typescript // 例如:操作日志注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OpLog { String value() default ""; // 描述:比如「创建订单」「删除用户」 } ``` #### 2)在业务方法上使用 ```java @Service public class OrderService { @OpLog("创建订单") public void createOrder(String userId, String productId) { // 业务逻辑... } @OpLog("取消订单") public void cancelOrder(String orderId) { // 业务逻辑... } } ``` #### 3)编写切面:通过 `@annotation` 匹配 ```java @Aspect @Component public class OpLogAspect { @Around("@annotation(opLog)") public Object logOperation(ProceedingJoinPoint joinPoint, OpLog opLog) throws Throwable { String desc = opLog.value(); String method = joinPoint.getSignature().toShortString(); long start = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long cost = System.currentTimeMillis() - start; System.out.printf("【操作日志】%s - 方法:%s - 耗时:%d ms - 状态:成功%n", desc, method, cost); return result; } catch (Throwable ex) { long cost = System.currentTimeMillis() - start; System.out.printf("【操作日志】%s - 方法:%s - 耗时:%d ms - 状态:异常:%s%n", desc, method, cost, ex.getMessage()); throw ex; } } } ``` **这样做的好处:** - 业务代码上只需要加一个注解,语义非常清晰; - AOP 切面只关心「带这个注解的方法」,**和包结构解耦**; - 方便按“功能”划分切面:日志切面、鉴权切面、限流切面、分布式锁切面…… --- ⬅️ [[02-依赖注入(DI)|依赖注入(DI)]] 🏠 [[00-Java|00-Java]] ➡️ [[04-Bean生命周期与三级缓存|04-Bean生命周期与三级缓存]]